package com.yuyang.gulimall.seckill.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.yuyang.common.to.mq.SeckillOrderTo;
import com.yuyang.common.utils.R;
import com.yuyang.common.vo.MemberResponseVo;
import com.yuyang.gulimall.seckill.feign.CouponFeignService;
import com.yuyang.gulimall.seckill.feign.ProductFeignService;
import com.yuyang.gulimall.seckill.interceptor.LoginUserInterceptor;
import com.yuyang.gulimall.seckill.service.SeckillService;
import com.yuyang.gulimall.seckill.to.SecKillSkuRedisTo;
import com.yuyang.gulimall.seckill.vo.SeckillSesssionsWithSkus;
import com.yuyang.gulimall.seckill.vo.SeckillSkuVo;
import com.yuyang.gulimall.seckill.vo.SkuInfoVo;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.sql.Date;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @author yu yang
 * @date 2023/2/13 -15:25
 */
@Service
public class SeckillServiceImpl implements SeckillService {
    @Autowired
    CouponFeignService couponFeignService;
    @Autowired
    StringRedisTemplate stringRedisTemplate;
    @Autowired
    ProductFeignService productFeignService;
    @Autowired
    RedissonClient redissonClient;
    @Autowired
    RabbitTemplate rabbitTemplate;

    private final String SESSIONS_CACHE_PREFIX = "seckill:sessions:";
    private final String SKUKILL_CACHE_PREFIX = "seckill:skus";

    private final String SKU_STOCK_SEMAPHORE = "seckill:stock:";//+商品随机码

    @Override
    public void uploadSeckillSkuLatest3Days() {
        R upload = couponFeignService.upload();
        if (upload.getCode() == 0) {
            List<SeckillSesssionsWithSkus> data = upload.getData(new TypeReference<List<SeckillSesssionsWithSkus>>() {
            });
            //缓存到Redis中
            if (data != null && data.size() > 0) {
                //秒杀的场次信息
                saveSessionInfo(data);
                //秒杀商品信息
                saveSkuInfo(data);
            }

        }
    }

    @Override
    public List<SecKillSkuRedisTo> getCurrentSeckillSkus() {
        Set<String> sessionsKey = stringRedisTemplate.keys(SESSIONS_CACHE_PREFIX + "*");
        if (sessionsKey != null && sessionsKey.size() > 0) {
            long time = new java.util.Date().getTime();
            for (String key : sessionsKey) {
//            "seckill:sessions:1676217600000_1676304000000"
                String[] split = key.split(":")[2].split("_");
                long startTime = Long.parseLong(split[0]);
                long endTime = Long.parseLong(split[1]);
                if (time >= startTime && time <= endTime) {
                    List<String> range = stringRedisTemplate.opsForList().range(key, -100, 100);
                    if (range != null) {
                        BoundHashOperations<String, String, String> hashOps = stringRedisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);
                        List<String> seckillSkus = hashOps.multiGet(range);
                        if (seckillSkus != null) {
                            List<SecKillSkuRedisTo> collect = seckillSkus.stream().map(seckillSku -> JSON.parseObject(seckillSku, SecKillSkuRedisTo.class)).collect(Collectors.toList());
                            return collect;
                        }
                    }

                }
            }
        }

        return null;
    }

    @Override
    public SecKillSkuRedisTo getSkuInfoSeckill(Long skuId) {
        BoundHashOperations<String, String, String> hashOps = stringRedisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);
        String regex = "\\d_" + skuId;
        Set<String> keys = hashOps.keys();
        if (keys != null && keys.size() > 0) {
            for (String key : keys) {
                if (Pattern.matches(regex, key)) {
                    String json = hashOps.get(key);
                    SecKillSkuRedisTo to = JSON.parseObject(json, SecKillSkuRedisTo.class);
                    long time = new java.util.Date().getTime();
                    if (to.getStartTime() > time || to.getEndTime() < time) {
                        to.setRandomCode(null);
                    }
                    return to;
                }
            }
        }

        return null;
    }

    // TODO 上架秒杀商品的时候，每一个数据都有过期时间。
    // TODO 秒杀后续的流程，简化了收货地址等信息。
    @Override
    public String kill(String killId, String key, Integer num) {
        MemberResponseVo memberResponseVo = LoginUserInterceptor.loginUser.get();
        Long memberId = memberResponseVo.getId();
        BoundHashOperations<String, String, String> hashOps = stringRedisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);
        String json = hashOps.get(killId);
        SecKillSkuRedisTo to = JSON.parseObject(json, SecKillSkuRedisTo.class);
        if (to != null) {
            //1、时间合法
            long time = new java.util.Date().getTime();
            if (time >= to.getStartTime() && time <= to.getEndTime()) {
                //2、商品随机码相同
                String sessionSkuKey = to.getPromotionSessionId() + "_" + to.getSkuId();
                if (key.equals(to.getRandomCode()) && killId.equals(sessionSkuKey)) {
                    //3、购物数量
                    if (num <= to.getSeckillLimit()) {
                        //4、验证这个人是否已经购买过
                        Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent("seckill:user", memberId + "_" + sessionSkuKey,  to.getEndTime() - time, TimeUnit.MILLISECONDS);
                        if (Boolean.TRUE.equals(aBoolean)) {
                            //库存
                            RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + key);
                            if (semaphore.tryAcquire(num)) {
                                //秒杀成功
                                SeckillOrderTo seckillOrderTo = new SeckillOrderTo();
                                seckillOrderTo.setOrderSn(IdWorker.getTimeId());
                                seckillOrderTo.setNum(num);
                                seckillOrderTo.setSeckillPrice(to.getSeckillPrice());
                                seckillOrderTo.setSkuId(to.getSkuId());
                                seckillOrderTo.setPromotionSessionId(to.getPromotionSessionId());
                                seckillOrderTo.setMemberId(memberId);
                                rabbitTemplate.convertAndSend("order-event-exchange", "order.seckill.order", seckillOrderTo);
                                return seckillOrderTo.getOrderSn();
                            }
                        }
                    }
                }
            }
        }

        return null;
    }

    private void saveSkuInfo(List<SeckillSesssionsWithSkus> data) {
        data.forEach(item -> {
            List<SeckillSkuVo> relationSkus = item.getRelationSkus();
            if (relationSkus != null) {

                BoundHashOperations<String, Object, Object> boundHashOps = stringRedisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);

                relationSkus.forEach(relationSku -> {
                    String key = relationSku.getPromotionSessionId() + "_" + relationSku.getSkuId();
                    if (Boolean.FALSE.equals(boundHashOps.hasKey(key))) {
                        SecKillSkuRedisTo secKillSkuRedisTo = new SecKillSkuRedisTo();
                        secKillSkuRedisTo.setStartTime(item.getStartTime().getTime());
                        secKillSkuRedisTo.setEndTime(item.getEndTime().getTime());
                        //商品秒杀随机码
                        String token = UUID.randomUUID().toString().replace("-", "");
                        secKillSkuRedisTo.setRandomCode(token);
                        //秒杀信息
                        BeanUtils.copyProperties(relationSku, secKillSkuRedisTo);
                        //skuInfo
                        R skuInfo = productFeignService.getSkuInfo(relationSku.getSkuId());
                        if (skuInfo.getCode() == 0) {
                            SkuInfoVo info = skuInfo.getData("skuInfo", new TypeReference<SkuInfoVo>() {
                            });
                            secKillSkuRedisTo.setSkuInfo(info);
                        }

                        String toJSONString = JSON.toJSONString(secKillSkuRedisTo);
                        boundHashOps.put(key, toJSONString);
                        //使用库存作为分布式的信号量  限流；
                        RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + token);
                        //商品可以秒杀的数量作为信号量
                        semaphore.trySetPermits(relationSku.getSeckillCount());
                        //TODO 设置过期时间。
                        semaphore.expireAt(item.getEndTime());
                    }
                });
            }
        });


    }

    private void saveSessionInfo(List<SeckillSesssionsWithSkus> data) {
        data.forEach(item -> {
            if (item.getRelationSkus() != null) {
                long startTime = item.getStartTime().getTime();
                long endTime = item.getEndTime().getTime();
                String key = SESSIONS_CACHE_PREFIX + startTime + "_" + endTime;
                if (Boolean.FALSE.equals(stringRedisTemplate.hasKey(key))) {
                    List<String> value = item.getRelationSkus().stream().map(relationSku -> relationSku.getPromotionSessionId() + "_" + relationSku.getSkuId()).collect(Collectors.toList());
                    stringRedisTemplate.opsForList().leftPushAll(key, value);
                    stringRedisTemplate.expireAt(key, new Date(endTime));
                }
            }
        });
    }
}
